package com.gitee.beiding.template_execel;

import javax.script.*;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class Js {

    private static class JsHolder {
        private ScriptEngine engine;
        private Bindings bindings;
    }

    private static List<String> functions = new ArrayList<>();

    private static List<String> initializedFunctions;

    private static ThreadLocal<JsHolder> jsHolderThreadLocal = new ThreadLocal<>();

    //创建定长的队列
    private static BlockingQueue<JsHolder> jsHolders = new ArrayBlockingQueue<>(Config.getCoreJsWorkerNumber());

    //在每次执行完成之后使用该方法Js
    static void recycle() {
        JsHolder jsHolder = jsHolderThreadLocal.get();
        if (jsHolder != null) {

            //执行必要的全局回收
            try {
                exe("_globalIndex=0");
            } catch (Exception e) {
            }

            jsHolders.offer(jsHolder);
            jsHolderThreadLocal.remove();
        }
    }


    private static JsHolder init() {

        JsHolder jsHolder = jsHolderThreadLocal.get();

        if (jsHolder == null) {
            jsHolder = jsHolders.poll();
            if (jsHolder == null) {
                if (initializedFunctions == null) {
                    synchronized (Js.class) {
                        if (initializedFunctions == null) {
                            initializedFunctions = functions;
                        }
                    }
                }

                //放入线程占位
                ScriptEngineManager manager = new ScriptEngineManager();
                ScriptEngine engine = manager.getEngineByName("nashorn");
                Bindings bindings = engine.getBindings(ScriptContext.GLOBAL_SCOPE);

                jsHolder = new JsHolder();
                jsHolder.engine = engine;
                jsHolder.bindings = bindings;
                jsHolderThreadLocal.set(jsHolder);

                //初始化脚本
                try {
                    set("console", new Console());
                    set("_java", new Java());
                    InputStream stream = Js.class.getClassLoader().getResource("init.js").openConnection().getInputStream();
                    String read = FileUtils.read(stream);
                    engine.eval(read);

                    for (String function : initializedFunctions) {
                        exe(function);
                    }

                } catch (Exception e) {
                    throw new Error(e);
                }
            } else {
                //绑定值
                jsHolderThreadLocal.set(jsHolder);
            }
        }

        return jsHolder;

    }

    //不能直接执行某些代码,防止引擎内部声明污染
    static Object exe(String js) throws ScriptException {
        try {
            // System.out.println(js);
            return init().engine.eval(js);
        } catch (Exception e) {
            throw e;
        }

    }

    public static Object call(String method, Object... p) {
        try {
            return ((Invocable) init().engine).invokeFunction(method, p);
        } catch (ScriptException | NoSuchMethodException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static Object set(String k, Object v) {
        return init().bindings.put(k, v);
    }


    public static Object put(String k, Object v) {
        return init().bindings.put(k, v);
    }

    /**
     * 由于引擎是线程隔离的,调用该方法定义的函数只对晚于调用该方法初始化的引擎才是有效的
     *
     * @param s 定义的函数
     * @throws ScriptException 如果是错误的脚本将会抛出该异常
     */

    //定义一个函数
    public static void defineFunction(String s) throws ScriptException {

        synchronized (Js.class) {
            if (functions == null) {
                throw new RuntimeException("请在Js使用前定义函数");
            }
        }

        functions.add(FunctionUtils.handleCacheableFunction(s));
    }

    public static Object remove(String k) {
        return init().bindings.remove(k);
    }

    public static Object get(String k) {
        return init().bindings.get(k);
    }

    public static void putAll(Map<String, Object> all) {
        init().bindings.putAll(all);
    }

    //提供控制台相关的功能
    public static class Console {
        public void log(String obj) {
            System.out.println(call("objToString", obj));
        }

        public void log() {
            System.out.println();
        }
    }

    //提供缓存操作
    private static class CacheTree {

        private static class Node {
            private Map<Object, Node> subMap;
            private Object value;
        }

        //TODO  第一级参数
        private Map<Object, Node> subMap;

        private Object value;

        void set(Object[] args, Object data) {
            if (args.length == 0) {
                value = data;
                return;
            }

            if (subMap == null) {
                subMap = new HashMap<>();
            }

            //从第一级参数开始递归
            Map<Object, Node> sm = subMap;

            Node node = null;

            //一直找到最后一级参数
            for (Object key : args) {
                node = sm.computeIfAbsent(key, k -> new Node());
                if (node.subMap == null) {
                    node.subMap = new HashMap<>();
                }
                sm = node.subMap;
            }

            node.value = data;

        }

        Object get(Object[] args) {
            if (args.length == 0) {
                return value;
            }
            if (subMap == null) {
                return null;
            }
            Map<Object, Node> sm = subMap;

            Node node = null;

            //一直找到最后一级参数
            for (Object key : args) {
                node = sm.get(key);
                if (node == null || node.subMap == null) {
                    return null;
                }
                sm = node.subMap;
            }

            return node.value;
        }

    }

    //对js提供java底层的操作
    public static class Java {

        //缓存数
        private Map<String, CacheTree> treeMap = new HashMap<>();

        public void cachePut(String name, Object[] args, Object data) {
            CacheTree cacheTree = treeMap.computeIfAbsent(name, k -> new CacheTree());
            cacheTree.set(args, data);
        }

        public Object cacheGet(String name, Object[] args) {

            CacheTree cacheTree = treeMap.get(name);
            if (cacheTree == null) {
                return null;
            }

            return cacheTree.get(args);
        }

        //清空现有缓存
        public void clearCache() {
            treeMap = new HashMap<>();

            //辅助gc
            System.gc();

        }


    }


}
